package abc.utils;

import lombok.extern.slf4j.Slf4j;
import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.Yaml;

import java.io.*;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

@Slf4j
public class YamlUtil {
    private static Map<String, String> configMap = new HashMap<String, String>();
    private static Map<String, Object> map;

    public static void loadConfig(String configName) throws FileNotFoundException {
        InputStream configStream = getApplicationConfigStream(configName);
        Yaml yml = new Yaml();
        configMap = new HashMap<>();
        map = yml.loadAs(configStream, HashMap.class);
        fillKeyValue("", map);
    }

    public static void saveConfig(String filePath) throws IOException {
        if(map == null || map.size()<1) {
            return;
        }
        FileWriter writer = new FileWriter(filePath, Charset.forName("UTF-8"));
        Yaml yml = new Yaml(new DumperOptions() {{
            setAllowUnicode(true);
            setSplitLines(true);
        }});
        yml.dump(map, writer);
    }

    private static synchronized void fillKeyValue(String key, Map<String, Object> map) {
        for(Map.Entry<String, Object> entry : map.entrySet()) {

            String currentKey = String.format("%s%s", StringUtil.isEmpty(key) ? key : key + ".", entry.getKey());
            if(entry.getValue() instanceof Map) {
                fillKeyValue(currentKey, (Map<String, Object>) entry.getValue());
            }

            else {
                configMap.put(currentKey, entry.getValue().toString());
            }
        }
    }

    public static String getText(String key, int indent, String content) {
        Map<String, Object> objectMap = getMap(key);
        if(objectMap == null) {
            return content;
        }
        for (Map.Entry<String, Object> entry : objectMap.entrySet()) {
            for(int i=0; i<indent; i++) {
                content += " ";
            }
            content += entry.getKey() + ": ";
            if(entry.getValue() instanceof String) {
                content += entry.getValue().toString() + "\r\n";
            }
            else if(ValueUtil.isPrimitive(entry.getValue())) {
                content += entry.getValue().toString() + "\r\n";
            }
            else {
                content += "\r\n";
                content = getText(String.format("%s.%s", key, entry.getKey()), indent+2, content);
            }
        }

        return content;
    }

    public static Map<String, Object> getMap(String key) {
        return getMap(key, map);
    }

    public static Map<String, Object> getMap(String key, Map<String, Object> data) {
        if(data == null || data.size() < 1) {
            return null;
        }

        List<String> keys = StringUtil.splitString2List(key, "\\.");
        if(keys.size() > 1) {
            if(data.containsKey(keys.get(0)) && data.get(keys.get(0)) != null && data.get(keys.get(0)) instanceof Map) {
                data = (Map<String, Object>) data.get(keys.get(0));
                return getMap(StringUtil.join(".", keys.subList(1, keys.size())), data);
            }
        }

        else if(keys.size() == 1){
            if(data.containsKey(key) && data.get(key) != null && data.get(key) instanceof Map) {
                return (Map<String, Object>) data.get(key);
            }
        }
        return null;
    }

    public static InputStream getApplicationConfigStream(String confingFilename) throws FileNotFoundException {
        File file = new File(System.getProperty("user.dir") + File.separator + "abc-api" + File.separator+"src"+File.separator+"main"+File.separator+"resources"+File.separator + confingFilename+ ".yml");
        if(file.exists()) {
            return new FileInputStream(file);
        }
        else {
            file = new File(System.getProperty("user.dir") + File.separator + "abc-api" + File.separator+"src"+File.separator+"main"+File.separator+"resources"+File.separator + confingFilename+ ".yaml");
        }

        if("application".equals(confingFilename)) {
            if(file.exists()) {
                return new FileInputStream(file);
            }
            else {
                file = new File(System.getProperty("user.dir") + File.separator + confingFilename+ ".yml");
            }
        }

        if(file.exists()) {
            return new FileInputStream(file);
        }
        else {
            file = new File(System.getProperty("user.dir") + File.separator+"src"+File.separator+"main"+File.separator+"resources"+File.separator + confingFilename+ ".yml");
        }

        if(file.exists()) {
            return new FileInputStream(file);
        }
        else {
            file = new File(System.getProperty("user.dir") + File.separator+"src"+File.separator+"main"+File.separator+"resources"+File.separator + confingFilename+ ".yaml");
        }

        if(file.exists()) {
            return new FileInputStream(file);
        }

        try {
            return Thread.currentThread().getContextClassLoader().getResourceAsStream( confingFilename + ".yml");
        }
        catch (Exception e) {
            return Thread.currentThread().getContextClassLoader().getResourceAsStream( confingFilename + ".yaml");
        }
    }

    public static Boolean containsKey(String key) {
        List<String> keys = StringUtil.splitString2List(key, "\\.");
        return containsKey(keys, map);
    }

    private static Boolean containsKey(List<String> keys, Map<String, Object> currentMap) {
        if (keys.size() < 1) {
            return false;
        } else if (keys.size() == 1) {
            return currentMap.containsKey(keys.get(0));
        } else {
            Iterator var3 = currentMap.entrySet().iterator();

            Map.Entry entry;
            do {
                if (!var3.hasNext()) {
                    return false;
                }

                entry = (Map.Entry)var3.next();
            } while(!((String)entry.getKey()).equals(keys.get(0)));

            if (entry.getValue() instanceof Map) {
                keys.remove(0);
                return containsKey(keys, (Map)entry.getValue());
            } else {
                return false;
            }
        }
    }

    public static void set(String key, String value) {
        configMap.put(key, value);
        List<String> keys = StringUtil.splitString2List(key, "\\.");
        Map tempMap = map;

        for(int i = 0; i < keys.size(); ++i) {
            if (i == keys.size() - 1) {
                ((Map)tempMap).put(keys.get(i), value);
            } else if (!((Map)tempMap).containsKey(keys.get(i))) {
                Map newMap = new HashMap();
                ((Map)tempMap).put(keys.get(i), newMap);
                tempMap = newMap;
            }
        }

    }

    public static String get(String key) throws IOException {
        List<String> keys = StringUtil.splitString2List(key, ":");
        return keys.size() == 2 ? get(keys.get(0), keys.get(1)) : get(key, String.class);
    }

    private static <T> T get(String key, T defaultValue) {
        if (configMap != null && configMap.size() >= 1) {
            defaultValue = configMap.containsKey(key) && configMap.get(key) != null && StringUtil.isNotEmpty(((String)configMap.get(key)).toString()) ? (T) StringUtil.cast(defaultValue.getClass(), ((String)configMap.get(key)).toString()) : defaultValue;
        } else {
            return defaultValue;
        }

        String value = defaultValue.toString();
        if(defaultValue instanceof String) {
            Matcher m = Pattern.compile("\\$\\{([\\w\\.\\-_]+)\\}").matcher(value);
            while (m.find()) {
                try {
                    value = value.replace(String.format("${%s}", m.group(1)), get(m.group(1), String.class));
                } catch (IOException e) {
                    log.error(e.getMessage(), e);
                }
            }
            if(StringUtil.isNotEmpty(value)) {
                return (T) value;
            }
        }

        return defaultValue;
    }

    public static <T> T get(String key, Class<T> clazz) throws IOException {
        if (configMap != null && configMap.size() >= 1) {
            if(configMap.get(key) == null) {
                return null;
            }

            T val = (clazz != null && clazz.equals(((String) configMap.get(key)).getClass()) ? (T) configMap.get(key) : JacksonUtil.convert((String) configMap.get(key), clazz));
            if(String.class.equals(clazz)) {
                String value = val.toString();
                Matcher m = Pattern.compile("\\$\\{([\\w\\.\\-_]+)\\}").matcher(value);
                while (m.find()) {
                    try {
                        value = value.replace(String.format("${%s}", m.group(1)), get(m.group(1), String.class));
                    } catch (IOException e) {
                        log.error(e.getMessage(), e);
                    }
                }
                if(StringUtil.isNotEmpty(value)) {
                    return (T) value;
                }
            }
            return val;
        } else {
            return null;
        }
    }
}
